package com.jse;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

import com.jse.anno.Autowried;
import com.jse.anno.Controller;
import com.jse.anno.RequestMapping;
import com.jse.web.JavaRoute;
import com.jse.web.Route;
import com.jse.web.Router;

public interface Ioc{
	
	static final Map<String, Object> BEAN_MAP = new HashMap<>();
	public final static Map<Class<?>,Object> BEANS=new ConcurrentHashMap<>();
	public final static Map<String,Class<?>> BEAN_NAMES=new ConcurrentHashMap<>();
	public final static Map<String,Class<?>> controllers =new ConcurrentHashMap<>();
	
	public static <T> T get(String name) {return (T)Js.get(name);}
	public static Object put(String name,Object o) {return Js.put(name,o);}
	public static <T> T getBean(String name,Class<T> c) {return (T)Js.get(name);}
	public static boolean containsKey(String name) {return Js.containsKey(name);}
	public static Object reg(String name,Object o) {return put(name, o);}
	public static Collection<Object> beans() {return Js.beans();}
	public static Set<Entry<String, Object>> entrys(){return Js.entrys();}
	
	public static <T> T getSingleton(Class<T> clazz) {
        //1.判断容器中是否有需要的Bean
        if (BEAN_MAP.containsKey(clazz.toString())){
            return (T)BEAN_MAP.get(clazz.toString());
        }
        //2.容器中无需要的Bean，递归创建
        //2.1.判断是不是Controller，如果是，创建Handler
        if (clazz.isAnnotationPresent(Controller.class)){
            String classPath = clazz.getAnnotation(Controller.class).path();
            Method[] methods = clazz.getMethods();
            for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];
                if (method.isAnnotationPresent(RequestMapping.class)){
                    String methodPath = method.getAnnotation(RequestMapping.class).value();
                    if("".equals(methodPath))methodPath="/"+method.getName();
                    String path=classPath+methodPath;
                    Router.addRoute(new JavaRoute(path,clazz,method));
                }
            }
        }
        try {//2.2.创建Bean
            T t = clazz.newInstance();
            Field[] fields = clazz.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                Field field = fields[i];
                field.setAccessible(true);
                //判断是否被Autowried注解
                if (field.isAnnotationPresent(Autowried.class)){
                    field.set(t,getSingleton(field.getType()));
                }
            }
            BEAN_MAP.put(clazz.toString(),t);
            return t;
        }catch (Exception e){
            System.out.println(e.toString());
            return null;
        }
    }
	
	public final static Map<String,Class<?>> classes=new HashMap<>();
	
	public static Map<String,Class<?>> scan(String pkg){
		if(classes.size()==0&&!pkg.isEmpty()) {
			try {
				var urls=Lang.loader().getResources(pkg.replace('.','/'));
				while (urls.hasMoreElements()) {
					var url=urls.nextElement();
					loadscan(url,pkg);
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		} 
		return classes;
	}
	public static Class<?> loadClass(String className){
		try {
			return Lang.loader().loadClass(className);
		} catch (ClassNotFoundException e) {
			throw new RuntimeException(e);
		}
	}
	public static void loadscan(URL url,String pkg) throws IOException {
		var protocol=url.getProtocol();
		if(protocol.equals("file")) {
			var pkgPath=url.getPath().replace("%20"," ");
			if(File.separatorChar=='\\')//win 下
				pkgPath=pkgPath.substring(1);
			try (var paths = Files.walk(Paths.get(pkgPath))) {
				paths
			    .filter((p)->{
			    	return Files.isRegularFile(p)&&p.toString().endsWith(".class");
			    })
			    .forEach((p)->{
			    	var f=p.toUri().toString().replace('/','.');
			    	var className=f.substring(f.lastIndexOf(pkg),f.length()-6);
			    	try {
			    	var clazz=loadClass(className);
//			    	regBean(clazz);
			    	classes.put(className,clazz);
			    	}catch(Throwable e) {
//			    		log.warn("load src Class err! [{}]",className);
			    	}
			    });
			}
			//add
		}else if(protocol.equals("jar")) {
			var conn=(JarURLConnection)url.openConnection();
			if(conn!=null) {
				var jarFile= conn.getJarFile();
				if(jarFile!=null) {
					var entries=jarFile.entries();
					while (entries.hasMoreElements()) {
						var entry=entries.nextElement();
						var entryName=entry.getName();
						if(entryName.endsWith(".class")&&!entryName.endsWith("module-info.class")) {
							var className=entryName.substring(0,entryName.length()-6)
									.replace('/','.');
							try {
								var clazz=loadClass(className);
//								regBean(clazz);
								classes.put(className,clazz);
							} catch (Throwable e) {
//								log.warn("load jar Class err! [{}]",className);
							}
						}
					}
				}
			}
		}
	}
	
	public static <T> T newInstance(Class<T> clazz,Object...args) {
		try {
			if(args==null||args.length==0) {
				return clazz.newInstance();
			}
			var constructors=clazz.getConstructors();
			for (int i = 0; i < constructors.length; i++) {
				var constructor=constructors[i];
				if(constructor.getParameterCount()==args.length) {
					return (T)constructor.newInstance(args);
				}
			}
		} catch (InstantiationException | IllegalAccessException |
				IllegalArgumentException | InvocationTargetException e) {
			e.printStackTrace();
		}
		return null;
	}
	public static <T> T newInstance(final String className, final Class<T> clazz, final Object... args)
	   {
	      if (className == null) {
	         return null;
	      }
	      try {
	         var loaded = Ioc.class.getClassLoader().loadClass(className);
	         if (args.length == 0) {
	            return clazz.cast(loaded.getDeclaredConstructor().newInstance());
	         }
	         var argClasses = new Class<?>[args.length];
	         for (int i = 0; i < args.length; i++) {
	            argClasses[i] = args[i].getClass();
	         }
	         var constructor = loaded.getConstructor(argClasses);
	         return clazz.cast(constructor.newInstance(args));
	      }
	      catch (Exception e) {
	         throw new RuntimeException(e);
	      }
	   }
	
	public static Object invoke(Object o,Method m,Object...args) {
		try {
			m.setAccessible(true);
			return m.invoke(o, args);
		} catch (IllegalAccessException | InvocationTargetException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	public static void setField(Object o,Field field,Object val) {
		try {
			field.setAccessible(true);
			field.set(o,val);
		} catch (IllegalArgumentException | IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public static void addBean(Class<?> clazz) {
		String name=clazz.getSimpleName();
		name=Character.toLowerCase(name.charAt(0))+name.substring(1);
		addBean(name, newInstance(clazz));
	}
	
	public static void addBean(Class<?> clazz,Object bean) {
		String name=clazz.getSimpleName();
		name=Character.toLowerCase(name.charAt(0))+name.substring(1);
		addBean(name, bean);
	}
	
	public static void addBean(String name,Object bean) {
		BEAN_NAMES.put(name,bean.getClass());
		BEANS.put(bean.getClass(),bean);
//		log.debug("add bean "+name+" "+bean.getClass());
	}
	
//	public static void regBean(Class<?> clazz) {
//		if(clazz.isAnnotationPresent(Bean.class)){
//			var name=clazz.getDeclaredAnnotation(Bean.class).value();
//			if(name.length()==0) {
//				name=clazz.getSimpleName();
//				name=Character.toLowerCase(name.charAt(0))+name.substring(1);
//			}
//			BEAN_NAMES.put(name,clazz);
//			BEANS.put(clazz, newInstance(clazz));
//		}else if(clazz.isAnnotationPresent(Action.class)){
//			var name=clazz.getDeclaredAnnotation(Action.class).value();
//			if(name.length()==0) {
//				name=clazz.getSimpleName();
//				name=Character.toLowerCase(name.charAt(0))+name.substring(1);
//			}
//			controllers.put(name,clazz);
//			BEAN_NAMES.put(name,clazz);
//			BEANS.put(clazz, newInstance(clazz));
//		}else if(clazz==classx||clazz.isAnnotationPresent(Conf.class)){
//			Object o=newInstance(clazz);
//			Method[]ms= clazz.getDeclaredMethods();
//			for (int i = 0; i < ms.length; i++) {
//				var m=ms[i];
//				if(m.isAnnotationPresent(Bean.class)) {
//					BEAN_NAMES.put(m.getName(),m.getReturnType());
//					BEANS.put(clazz,invoke(o,m));
//				}
//			}
//		}
//	}
	
	public static Object getBean(String name) {
		return BEANS.get(BEAN_NAMES.get(name));
	}
	
	
	public static void inject() {
		for (var entry : BEANS.entrySet()) {
			var beanClass=entry.getKey();
			var bean = entry.getValue();
			var fields=beanClass.getDeclaredFields();
			for (var field : fields) {
				if(field.isAnnotationPresent(Autowried.class)) {
					var fieldClass=field.getType();
					var fieldBean=getBean(field.getName(), fieldClass);
					if(fieldBean!=null) {
						setField(bean,field,fieldBean);
					}
				}
			}
		}
	}
	
	public static void mvc() {
		for (var entry : controllers.entrySet()) {
			var actionClass = entry.getValue();
			var methods=actionClass.getDeclaredMethods();
			for (var m:methods) {
				if(m.isAnnotationPresent(Controller.class)) {
					var mapping=m.getAnnotation(Controller.class);
					var path=mapping.path();//请求映射
					var method=mapping.method();//请求方法
//					var route=new Route(path,method,(f)->{
//						try {
//							var args=new Object[m.getParameterCount()];
//							var paramtypes=m.getParameters();
//							for(var i=0;i<paramtypes.length;i++) {
//								var p=paramtypes[i];
//								var name=p.getName();
//								var type=p.getType();
//								args[i]=null;
//								//System.out.println(path+":"+i+"=="+p.getType());
//							}
//							return m.invoke(getBean(actionClass),args);
//						} catch (IllegalAccessException | InvocationTargetException e) {
//							// TODO Auto-generated catch block
//							e.printStackTrace();
//							return null;
//						}
//					});
////					route.run(1);
//					routes.put(method+"|"+path, route);
				}
			}
			
		}
	}

	static final Pattern GETTER_PATTERN = Pattern.compile("(get|is)[A-Z].+");
	public static Set<String> getPropertyNames(final Class<?> targetClass)
	   {
	      var set = new HashSet<String>();
	      var matcher = GETTER_PATTERN.matcher("");
	      for (var method : targetClass.getMethods()) {
	         var name = method.getName();
	         if (method.getParameterTypes().length == 0 && matcher.reset(name).matches()) {
	            name = name.replaceFirst("(get|is)", "");
	            try {
	               if (targetClass.getMethod("set" + name, method.getReturnType()) != null) {
	                  name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
	                  set.add(name);
	               }
	            }
	            catch (Exception e) {
	               // fall thru (continue)
	            }
	         }
	      }
	      return set;
	   }
	
	public static Object getProperty(final String propName, final Object target)
	   {
	      try {
	         // use the english locale to avoid the infamous turkish locale bug
	         var capitalized = "get" + propName.substring(0, 1).toUpperCase(Locale.ENGLISH) + propName.substring(1);
	         var method = target.getClass().getMethod(capitalized);
	         return method.invoke(target);
	      }
	      catch (Exception e) {
	         try {
	            var capitalized = "is" + propName.substring(0, 1).toUpperCase(Locale.ENGLISH) + propName.substring(1);
	            var method = target.getClass().getMethod(capitalized);
	            return method.invoke(target);
	         }
	         catch (Exception e2) {
	            return null;
	         }
	      }
	   }
	public static boolean isPresent(String className) {
		return isPresent(className,Lang.loader());
	}
	public static boolean isPresent(String className, ClassLoader classLoader) {
		try {Class.forName(className, false, classLoader);return true;
		} catch (ClassNotFoundException ex) {return false;}
	}

	public static boolean has(String name) {
		return BEAN_NAMES.containsKey(name);
	}

	public static Set<Method> getMethodsAnnotatedWith(Class<?> clazz, Class<? extends Annotation> annotation) {
		Set<Method> methods = new HashSet<>();
		Class<?> actualClass = getActualClass(clazz);
		if (actualClass == null) {
			return methods;
		}
		for (Method method : actualClass.getDeclaredMethods()) {
			if (method.isAnnotationPresent(annotation)) {
				methods.add(method);
			}
		}
		return methods;
	}
	/**
	 * 获取cglib代理的真实类
	 *
	 * @param clazz 代理类
	 * @return 类
	 */
	public static Class<?> getActualClass(Class<?> clazz) {
		Class<?> actualClass = clazz;
		while (isAsmClass(actualClass)) {
			actualClass = actualClass.getSuperclass();
		}
		return actualClass;
	}
	/**
	 * 验证类是否是asm,cglib生成类
	 *
	 * @param clazz the class to check
	 * @return true为是代理类
	 */
	public static boolean isAsmClass(Class<?> clazz) {
		return (clazz != null && isAsmClassName(clazz.getName()));
	}

	/**
	 * 验证类名是否为asm,cglib代理类名
	 *
	 * @param className the class name to check
	 * @return true为是代理类
	 */
	public static boolean isAsmClassName(String className) {
		return (className != null && className.contains("$$"));
	}
}
